//
//  FileManagerExtension.swift
//
//  Created by Evan Xie on 2018/5/14.

//

import Foundation

public typealias Block = () -> Void

extension FileManager {
    
    public typealias FileURLFilterBlock = (_ fileURL: URL, _ isDirectory: Bool) -> Bool
    
    // MARK: - Directories Management
    
    public static var documentDirectoryURL: URL {
        return URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
    }
    
    public static var applicationSupportDirectoryURL: URL {
        return URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true)[0])
    }
    
    public static var cachesDirectoryURL: URL {
        return URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0])
    }
    
    public static var libraryDirectoryURL: URL {
        return URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)[0])
    }
    
    public static var tempDirectoryURL: URL {
        return URL(fileURLWithPath: NSTemporaryDirectory())
    }
    
    public static func createParentDirectory(forFileURL fileURL: URL) throws {
        let parentURL = fileURL.deletingLastPathComponent()
        try FileManager.default.createDirectory(at: parentURL, withIntermediateDirectories: true, attributes: nil)
    }
    
    /** Enumerate all the file urls under the given directory url. */
    public static func fileURLs(at directoryURL: URL, filter: FileURLFilterBlock? = nil, options: FileManager.DirectoryEnumerationOptions = .skipsHiddenFiles) -> [URL] {
        
        var fileURLs = [URL]()
        let key = URLResourceKey.isDirectoryKey
        
        if let enumerator = FileManager.default.enumerator(at: directoryURL, includingPropertiesForKeys: [key], options: options, errorHandler: nil) {
            while let url = enumerator.nextObject() as? URL {
                if filter != nil {
                    let resourceValues = try! url.resourceValues(forKeys: Set<URLResourceKey>(arrayLiteral: key))
                    if filter!(url, resourceValues.isDirectory!) {
                        fileURLs.append(url)
                    }
                } else {
                    fileURLs.append(url)
                }
            }
        }
        
        return fileURLs
    }
}

extension FileManager {
    
    // MARK: - File Size & Disk Free Space
    
    public typealias LocalDiskUsage = (freeSpace: UInt64, totalSpace: UInt64)
    
    /** Get the file size for given file url. If given url is directory url, the file size is 0. */
    public static func fileSizeAtURL(_ fileURL: URL) -> UInt64 {
        guard let attrs = try? FileManager.default.attributesOfItem(atPath: fileURL.path) else { return 0 }
        guard let fileSize = attrs[FileAttributeKey.size] as? UInt64 else { return 0 }
        return fileSize
    }
    
    /** Calculate the directory size, which includes all the files under the given directory url. */
    public static func directorySizeAtURL(_ directoryURL: URL) -> UInt64 {
        guard let enumerator = FileManager.default.enumerator(at: directoryURL, includingPropertiesForKeys: nil) else {
            return 0
        }
        
        var totalSize: UInt64 = 0
        while let url = enumerator.nextObject() as? URL {
            guard let attrs = try? FileManager.default.attributesOfItem(atPath: url.path) else { continue }
            guard let fileType = attrs[FileAttributeKey.type] as? FileAttributeType else { continue }
            guard fileType == .typeRegular else { continue }
            guard let fileSize = attrs[FileAttributeKey.size] as? UInt64 else { continue }
            totalSize += fileSize
        }
        return totalSize
    }
    
    /** Get the local disk free space and total space in bytes. */
    public static func localDiskUsage() -> LocalDiskUsage {
        guard let attrs = try? FileManager.default.attributesOfFileSystem(forPath: documentDirectoryURL.path) else { return (0, 0) }
        guard let freeSpace = attrs[FileAttributeKey.systemFreeSize] as? UInt64 else { return (0, 0) }
        guard let totalSapce = attrs[FileAttributeKey.systemSize] as? UInt64 else { return (freeSpace, 0) }
        return (freeSpace, totalSapce)
    }
}

extension FileManager {
    
    // MARK: - Local File Caches Management
    
    public typealias FileSizeBlock = (_ fileSize: UInt64) -> Void
    
    /**
     Asynchronousely calucate app cache size in bytes.
     
     Cache files are under below directory:
     - Caches
     - Tmp
     
     - Parameter cacheSizeBlock: Invoked on the main thread.
     */
    public static func calculateAppCahceSize(_ cacheSizeBlock: @escaping FileSizeBlock) {
        DispatchQueue.global().async {
            var cacheSize: UInt64 = 0
            cacheSize += directorySizeAtURL(FileManager.tempDirectoryURL)
            cacheSize += directorySizeAtURL(FileManager.cachesDirectoryURL)
            DispatchQueue.main.async { cacheSizeBlock(cacheSize) }
        }
    }
    
    /**
     Asynchronousely clear app cache files.
     
     Cache files are under below directory:
     - Caches
     - Tmp
     
     - Parameter completionBlock: Invoked on the main thread.
     */
    public static func clearAppCache(completionBlock: Block?) {
        DispatchQueue.global().async {
            clearTempDirectory()
            clearCachesDirectory()
            completionBlock?()
        }
    }
    
    public static func clearTempDirectory() {
        
        do {
            let urls = try FileManager.default.contentsOfDirectory(at: tempDirectoryURL, includingPropertiesForKeys: nil, options: [])
            for url in urls {
                try? FileManager.default.removeItem(at: url)
            }
        } catch {
            print("Clear temp directory failed: \(error)")
        }
    }
    
    public static func clearCachesDirectory() {
        
        do {
            let urls = try FileManager.default.contentsOfDirectory(at: cachesDirectoryURL, includingPropertiesForKeys: nil, options: [])
            for url in urls {
                try? FileManager.default.removeItem(at: url)
            }
        } catch {
            print("Clear Caches directory failed: \(error)")
        }
    }
}
